#ifndef SHELL_H
#define SHELL_H

#include <ctime>
#include <functional>
#include <map>
#include <string>
#include <vector>

#include "command.h"
#include "command_context.h"
#include "command_exit_codes.h"
#include "shell_fwd.h"
#include "vtr_range.h"
#include "vtr_vector.h"

/* Begin namespace openfpga */
namespace openfpga {

/*********************************************************************
 * Data structure to define a working environment of a mini shell
 *
 * An example of how to use
 * -----------------------
 * // Create a new shell with a name of 'OpenFPGA' which use data structure
 *OpenfpgaContext for data exchange Shell<OpenfpgaContext> shell("OpenFPGA");
 *
 * // Add a command 'read_arch' with a description
 * ShellCommandId cmd_read_arch = shell.add_command("read_arch", "Command to
 *read an architecture");
 * // Create a new category and classify the command to it
 * ShellCommandCategoryId cmd_category = shell.add_command_category("io");
 * shell.set_command_category(cmd_read_arch, cmd_category);
 *
 * // Add options to the command 'read_arch'
 * const Command* cmd_read_arch_obj = shell.command(cmd_read_arch);
 * // Detailed examples can be found in Command.h
 * ...
 *
 * // Add execute function to the command 'read_arch'
 * // This is the function to be called when the command is used
 * // Assume that you have a function read_arch() which does the job of read
 *architecture
 * // Note that the function read_arch() should return a void with only three
 *arguments
 * //   void read_arch(T, const Command&, const CommandContext&);
 * // The first argument is the template type you set when define the shell
 * // The second argument is a read-only object storing the options for the
 *command
 * // The third argument is a read-only object storing the parsing results for
 *the command shell.set_command_execute_function(cmd_read_arch, &read_arch);
 *
 * // Run a shell
 * shell.run();
 *
 ********************************************************************/
template <class T>
class Shell {
 public: /* Types */
  typedef vtr::vector<ShellCommandId, ShellCommandId>::const_iterator
    shell_command_iterator;
  /* Create range */
  typedef vtr::Range<shell_command_iterator> shell_command_range;
  /* Enumeration of command types in a shell
   * Built-in commands have their own execute functions inside the shell
   */
  enum e_exec_func_type {
    CONST_STANDARD, /* A standard function requires to read data from the
                       command context, need the shell to provide command
                       parsing */
    STANDARD,       /* A standard function requires to write data to the command
                       context, need the shell to provide command parsing */
    CONST_SHORT,    /* A short function requries to read data from the common
                       context without any command-line options */
    SHORT,    /* A short function requries to write data to the common context
                 without any command-line options */
    BUILTIN,  /* A built-in function which requires no input arguments at all */
    FLOATING, /* A floating function which does not need to write/read any data
                 from the common context. Need shell to provide command parsing
               */
    MACRO,    /* A black-box function which has its own command-line
                 parser/interface. No need for shell to provide command parsing */
    PLUGIN,   /* A plug-in function which is based on other commands, require
                 shell methods */
    NUM_EXEC_FUNC_TYPES
  };

 public: /* Constructor */
  Shell<T>();

 public: /* Public accessors */
  std::string name() const;
  std::string title() const;
  shell_command_range commands() const;
  ShellCommandId command(const std::string& name) const;
  std::string command_description(const ShellCommandId& cmd_id) const;
  ShellCommandClassId command_class(const ShellCommandId& cmd_id) const;
  std::string command_class_name(const ShellCommandClassId& cmd_class_id) const;
  /* We force a read-only return objects for command and command context
   * because users should NOT modify any of them!
   */
  const Command& command(const ShellCommandId& cmd_id) const;
  const CommandContext& command_context(const ShellCommandId& cmd_id) const;
  std::vector<ShellCommandId> command_dependency(
    const ShellCommandId& cmd_id) const;
  std::vector<ShellCommandId> commands_by_class(
    const ShellCommandClassId& cmd_class_id) const;

 public: /* Public mutators */
  void set_name(const char* name);
  void add_title(const char* title);
  ShellCommandId add_command(const Command& cmd, const char* descr,
                             const bool& hidden = false);
  void set_command_class(const ShellCommandId& cmd_id,
                         const ShellCommandClassId& cmd_class_id);
  /* Link the execute function to a command
   * We support here different types of functions to be executed in the shell
   * Users just need to specify the function object and its type will be
   * automatically inferred
   *
   * Note that all the function should return exit codes complying to the
   * shell_exit_code.h execept the internal functions
   */

  /* Standard function, including the data exchange <T> and commands
   * This function requires the data exchange <T> to be constant
   * This is designed for outputting functions requiring external data than the
   * <T>
   */
  void set_command_const_execute_function(
    const ShellCommandId& cmd_id,
    std::function<int(const T&, const Command&, const CommandContext&)>
      exec_func);

  /* Standard function, including the data exchange <T> and commands
   * This function allows modification to the data exchange <T>
   * This is designed for implementing functions requiring external data than
   * the <T>
   */
  void set_command_execute_function(
    const ShellCommandId& cmd_id,
    std::function<int(T&, const Command&, const CommandContext&)> exec_func);

  /* Short function, including only the data exchange <T>
   * This function requires the data exchange <T> to be constant
   * This is designed for outputting functions without external data than the
   * <T>
   */
  void set_command_const_execute_function(
    const ShellCommandId& cmd_id, std::function<int(const T&)> exec_func);

  /* Short function, including only the data exchange <T>
   * This function allows modification to the data exchange <T>
   * This is designed for internal implementing functions without external data
   * than the <T>
   */
  void set_command_execute_function(const ShellCommandId& cmd_id,
                                    std::function<int(T&)> exec_func);

  /* Built-in function, including only the shell envoriment variables */
  void set_command_execute_function(const ShellCommandId& cmd_id,
                                    std::function<void()> exec_func);

  /* Floating function, including the only commands
   * This is designed for implementing functions which is totally independent
   * from <T>
   */
  void set_command_execute_function(
    const ShellCommandId& cmd_id,
    std::function<int(const Command&, const CommandContext&)> exec_func);

  /* Marco function, which directly call a macro function without command
   * parsing */
  void set_command_execute_function(const ShellCommandId& cmd_id,
                                    std::function<int(int, char**)> exec_func);

  /* Plug-in function, which calls other command thru shell's APIs */
  void set_command_execute_function(
    const ShellCommandId& cmd_id,
    std::function<int(Shell<T>*, T&, const Command&, const CommandContext&)>
      exec_func);

  void set_command_dependency(
    const ShellCommandId& cmd_id,
    const std::vector<ShellCommandId>& cmd_dependency);
  ShellCommandClassId add_command_class(const char* name);

 public: /* Public validators */
  bool valid_command_id(const ShellCommandId& cmd_id) const;
  bool valid_command_class_id(const ShellCommandClassId& cmd_class_id) const;

 public: /* Public executors */
  /* Start the interactive mode, where users will type-in command by command */
  void run_interactive_mode(T& context, const bool& quiet_mode = false);
  /* Start the script mode, where users provide a file which includes all the
   * commands to run */
  void run_script_mode(const char* script_file_name, T& context,
                       const bool& batch_mode = false);
  /* Print all the commands by their classes. This is actually the help desk */
  void print_commands(const bool& show_hidden = false) const;
  /* Find the exit code (assume quit shell now) */
  int exit_code() const;
  /* Show statistics of errors during command execution */
  int execution_errors() const;
  /* Quit the shell */
  void exit(const int& init_err = 0) const;
  /* Execute a command, the command line is the user's input to launch a command
   * The common_context is the data structure to exchange data between commands
   * Optionally, hidden commands may be forbidden to call. User can allow them
   * to be called under different situations
   */
  int execute_command(const char* cmd_line, T& common_context,
                      const bool& allow_hidden_command = true);

 private: /* Internal data */
  /* Name of the shell, this will appear in the interactive mode */
  std::string name_;

  /* Title of the shell, this will appear in the interactive mode as an
   * introduction */
  std::string title_;

  /* Unique ids for each class of command */
  vtr::vector<ShellCommandClassId, ShellCommandClassId> command_class_ids_;

  /* Names for each class of command */
  vtr::vector<ShellCommandClassId, std::string> command_class_names_;

  /* Unique ids for each command */
  vtr::vector<ShellCommandId, ShellCommandId> command_ids_;

  /* If this is a hidden command which will not appear in help desk */
  vtr::vector<ShellCommandId, bool> command_hidden_;

  /* Objects for each command */
  vtr::vector<ShellCommandId, Command> commands_;

  /* Parsing results for each command */
  vtr::vector<ShellCommandId, CommandContext> command_contexts_;

  /* Description of the command, this is going to be printed out in the help
   * desk */
  vtr::vector<ShellCommandId, std::string> command_description_;

  /* Class ids for each command */
  vtr::vector<ShellCommandId, ShellCommandClassId> command_classes_;

  /* Function pointers to execute each command
   * We support here three types of functions to be executed in the shell
   * 1. Standard function, including the data exchange <T> and commands
   * 2. Short function, including only the data exchange <T>
   * 3. Built-in function, including only the shell envoriment variables
   * 4. Marco function, which directly call a macro function without command
   * parsing
   */
  vtr::vector<ShellCommandId, std::function<int(const T&, const Command&,
                                                const CommandContext&)>>
    command_const_execute_functions_;
  vtr::vector<ShellCommandId,
              std::function<int(T&, const Command&, const CommandContext&)>>
    command_standard_execute_functions_;
  vtr::vector<ShellCommandId, std::function<int(const T&)>>
    command_short_const_execute_functions_;
  vtr::vector<ShellCommandId, std::function<int(T&)>>
    command_short_execute_functions_;
  vtr::vector<ShellCommandId,
              std::function<int(const Command&, const CommandContext&)>>
    command_floating_execute_functions_;
  vtr::vector<ShellCommandId, std::function<void()>>
    command_builtin_execute_functions_;
  vtr::vector<ShellCommandId, std::function<int(int, char**)>>
    command_macro_execute_functions_;
  vtr::vector<ShellCommandId, std::function<int(Shell<T>*, T&, const Command&,
                                                const CommandContext&)>>
    command_plugin_execute_functions_;

  /* Type of execute functions for each command.
   * This is supposed to be an internal data ONLY
   */
  vtr::vector<ShellCommandId, e_exec_func_type> command_execute_function_types_;

  /* A flag to indicate if the command has been executed */
  vtr::vector<ShellCommandId, int> command_status_;

  /* Dependency graph for different commands,
   * This helps the shell interface to check if a command need other commands to
   * be run before its execution
   */
  vtr::vector<ShellCommandId, std::vector<ShellCommandId>>
    command_dependencies_;

  /* Fast name look-up */
  std::map<std::string, ShellCommandId> command_name2ids_;
  std::map<std::string, ShellCommandClassId> command_class2ids_;
  vtr::vector<ShellCommandClassId, std::vector<ShellCommandId>>
    commands_by_classes_;

  /* Timer */
  std::clock_t time_start_;
};

} /* End namespace openfpga */

/* Include the template implementation functions in the header file */
#include "shell.tpp"

#endif
